import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:dolqun_movies/StateManagement/ModelProvider.dart';
import 'package:dolqun_movies/UI/Pages/Models/CacheManagerModel.dart';
import 'package:dolqun_movies/UI/Pages/Models/ColorThemeModel.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:video_cache_manager/CacheManager.dart' show VideoManifest, HlsCacheManager;
import 'package:video_player/video_player.dart';
import 'dart:math' as Math;

class CustomVideoPlayer extends StatefulWidget {
  final String videoUrl;
  final String id;
  final String videoName;

  final void Function(dynamic error) onError;

  const CustomVideoPlayer({Key key, @required this.videoUrl, @required this.id, @required this.videoName, @required this.onError}) : super(key: key);
  @override
  State<StatefulWidget> createState() => _VideoPlayerState();
}

enum _PlayingState {
  paused,
  loading,
  playing,
}
class _VideoPlayerState extends State<CustomVideoPlayer> {
  Stream<int> _manifestLoadingProgress;
  Stream<int> _mediaSegmentLoadingProgress;
  int pointer = 0;
  List<int> cacheQueue = [];
  double _aspectRatio = 16 / 9;
  VideoPlayerController _videoPlayerController;
  VideoManifest manifest;
  Future<bool> _updateKey;
  bool _controlPanelVisible = true;
  _PlayingState state = _PlayingState.paused;
  var _videoSegments = <SliderBlock>[SliderBlock(enabled: false, length: 1.0)];
  void Function() timelineNotification = () {};
  double _seekPanelWidth = 0.0;

  GlobalKey _seekPanelKey = GlobalKey();

  @override
  void dispose() {
    this._manifestLoadingProgress = null;
    this._mediaSegmentLoadingProgress = null;
    this._videoPlayerController = null;
    this._updateKey = null;
    this.timelineNotification = null;
    print("Dispose of video player.");
    super.dispose();
  }
  Future<void> _initializeSegmentCacheAvailability() async {
    var count = await this.manifest.getSegmentCount();
    this._videoSegments = List.generate(count, (index) => SliderBlock(enabled: false, length: 0.0));
    var totalDuration = this.manifest.playlist.segments.map((e) => e.durationUs).reduce((a, b) => a + b);
    for (var index = 0; index < count; index++) {
      var block = SliderBlock(
          enabled: await manifest.segmentCacheAvailability(manifest.playlist.segments[index]),
          length: this.manifest.playlist.segments[index].durationUs / totalDuration
      );
      this._videoSegments[index] = block;
      setState(() {

      });
    }
  }

  Completer<void> _queueWaiter;
  Future<void> _cacheFromQueue() async {
    if (_queueWaiter == null) {
      _queueWaiter = Completer<void>();
      _queueWaiter.complete();
    }
    await _queueWaiter.future;

    _queueWaiter = Completer<void>();
    var processing = <int>[...this.cacheQueue];
    for (var indexOfShouldBeCache in processing) {
      var available = await manifest.segmentCacheAvailability(manifest.playlist.segments[indexOfShouldBeCache]);
      if (!available) await (){
        var completer = new Completer<void>();
        this._mediaSegmentLoadingProgress = manifest.cacheSegmentByIndex(indexOfShouldBeCache).asBroadcastStream();
        this._mediaSegmentLoadingProgress.listen((event) {}, onDone: () => completer.complete());
        return completer.future;
      }();
      this.cacheQueue = this.cacheQueue.where((element) => element != indexOfShouldBeCache).toList();
    }
    _queueWaiter.complete();
  }
  Future<void> ensurePlayerInitialized() async{
    if (!_videoPlayerController.value.initialized) await _videoPlayerController.initialize();
  }
  Future<void> cache() async {
    setState(() {
      this.state = _PlayingState.loading;
    });
    this.cacheQueue.add(pointer);
    await _cacheFromQueue();
    if (pointer < manifest.playlist.segments.length) {
      this.cacheQueue.add(pointer + 1);
      _cacheFromQueue();
    }
    await _initializeSegmentCacheAvailability();
    setState(() {
      this.state = _videoPlayerController.value.isPlaying ? _PlayingState.playing : _PlayingState.paused;
    });
  }
  @override
  Widget build(BuildContext context) {
    ModelContainer.of<ColorThemeModel>(context);
    var cacheManager = ModelContainer.of<CacheManagerModel>(context).model.cacheManager;
    this.manifest ??= VideoManifest(
        identifier: widget.id,
        url: widget.videoUrl,
      name: widget.videoName
    );
    var controlPanelVisible = this._controlPanelVisible && this.state != _PlayingState.loading;
    return AspectRatio(
      aspectRatio: _aspectRatio,
      child: StreamBuilder<int>(
        stream: _manifestLoadingProgress ??= cacheManager.cacheMediaManifest(manifest).asBroadcastStream(),
        builder: (context, snapshot) {
          if (snapshot.hasError) return Center(child: Text("An error occurred while playing video, here is error message: ${snapshot.error}", style: TextStyle(background: Paint()..color = Colors.red),),);
          if (snapshot.connectionState == ConnectionState.done) {
            return FutureBuilder<bool>(
              future: this._updateKey ??= () async {
                await manifest.updateKey();
//                print("Keys are updated.");
                return true;
              }(),
              builder: (context, snapshot){
                if (snapshot.hasError) return Center(child: Text("An error occurred while updating video authentication progress, here is the error message: ${snapshot.error}", style: TextStyle(background: Paint()..color = Colors.red)),);
                if (snapshot.hasData) {
                  this._videoPlayerController ??= () {
                    _initializeSegmentCacheAvailability();
                    var path = cacheManager.buildManifestFilePath(manifest);
                    var value = VideoPlayerController.file(File(path));
                    return value;
                  }();
                  return Stack(
                    children: <Widget>[
                      Positioned.fill(
                        child: GestureDetector(
                          onTap: (){
                            setState(() {
                              this._controlPanelVisible = !this._controlPanelVisible;
                            });
                          },
                          child: _VideoRender(
                            playerController: _videoPlayerController,
                            refresh: (){
                              setState(() {

                              });
                              var duration = this._videoPlayerController.value.position;
                              if (duration.inSeconds % 5 == 0) {
                                this.manifest.getMediaSegmentIndexByDuration(duration).then((value){
                                  if (value != pointer) {
                                    pointer = value;
                                    cache();
                                  }
                                });
                              }
                            },
                          ),
                        ),
                      ),
                      Positioned.fill(
                        child: Stack(
                          children: <Widget>[
                            controlPanelVisible ? Center(
                              child: _buildBlurFrame(child: IconButton(icon: Icon(_videoPlayerController.value.isPlaying ? Icons.pause_circle_outline : Icons.play_circle_outline, color: Colors.white.withOpacity(0.5),), onPressed: () async {
                                if (!_videoPlayerController.value.isPlaying) {
                                  await cache();
                                  await ensurePlayerInitialized();
                                }
                                setState(() {
                                  _videoPlayerController.value.isPlaying ? _videoPlayerController.pause() : _videoPlayerController.play();
                                });
                              },)),
                            ) : SizedBox(),
                            //Build loading media segment progress indicator
                            Builder(
                              builder: (context) {
                                var condition = state == _PlayingState.loading && this._mediaSegmentLoadingProgress != null && ListExtension.firstOrNull(cacheQueue) == pointer;
                                return condition ? _buildSegmentLoadingProgress() : SizedBox();
                              },
                            ),
                            controlPanelVisible ? _buildVideoSeekPanelWidgets() : SizedBox(),
                            controlPanelVisible ? _buildVolumeWidgets() : SizedBox()
                          ],
                        ),
                      ),
                    ],
                  );
                }
                return Center(child: Text("Writing video authentication key info..."),);
              },
            );
          }
          if (snapshot.hasError) return Center(child: Text(snapshot.data.toString()),);
          return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(Colors.white), strokeWidth: 0.5,),);
        },
      ),
    );
  }

  Widget _buildBlurFrame({Widget child, borderRadius: 15.0}) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(borderRadius),
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
        child: Container(
          color: Color(0xff808080).withOpacity(0.1),
          child: child,
        ),
      ),
    );
  }

  String _getStringWithDuration(Duration duration) {
    duration ??= Duration();
    var hour = duration.inSeconds / (60 * 60);
    var minute = (hour - hour.floor()) * 60;
    var second = (minute - minute.floor()) * 60;
    return "${hour.floor()}:${minute.floor()}:${second.floor()}";
  }

  Widget _buildVolumeWidgets() {
    return Positioned(
      left: 10.0,
      top: 10.0,
      bottom: 10.0 + 10.0 + 10.0,
      child: Center(
        child: _buildBlurFrame(
            child: SizedBox(
              height: 150.0,
              width: 40.0,
              child: Column(
                children: <Widget>[
                  IconButton(icon: Icon(Icons.volume_up, color: Colors.white.withOpacity(0.5),), onPressed: (){
                    var volume = this._videoPlayerController.value.volume;
                    if (volume < 1.0) {
                      volume += 0.2;
                      this._videoPlayerController.setVolume(volume ?? 0);
                      setState(() {

                      });
                    }
                  },),
                  Expanded(child: CustomPaint(
                    painter: MyLinePainter(
                        value: 1.0 - this._videoPlayerController.value.volume,
                        deactivatedColor: Colors.white,
                        activatedColor: Colors.white.withOpacity(0.1),
                        axis: Axis.vertical
                    ),
                    child: SizedBox(width: double.infinity, height: double.infinity,),
                  ),),
                  IconButton(icon: Icon(Icons.volume_down, color: Colors.white.withOpacity(0.5),), onPressed: (){
                    var volume = this._videoPlayerController.value.volume;
                    if (volume > 0.0) {
                      volume -= 0.2;
                      this._videoPlayerController.setVolume(volume);
                      setState(() {

                      });
                    }
                  },)
                ],
              ),
            )
        ),
      ),
    );
  }

  Widget _buildVideoSeekPanelWidgets() {
    var position = (this._videoPlayerController.value.position?.inMilliseconds ?? 1) / (this._videoPlayerController.value.duration?.inMilliseconds ?? 1);
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
      this._seekPanelWidth = this._seekPanelKey.currentContext.size.width;
    });
    return Positioned(
      bottom: 10.0,
      left: 10.0,
      right: 10.0,
      height: 30.0,
      child: _buildBlurFrame(
          child: Row(
            children: <Widget>[
              SizedBox(width: 5.0,),
              Text(this._getStringWithDuration(this._videoPlayerController.value.duration,), textAlign: TextAlign.center,),
              SizedBox(width: 5.0,),
              Expanded(
                key: this._seekPanelKey,
                child: GestureDetector(
                  onTapUp: (details) async {
                    var seekPosition = ((this.manifest.playlist.durationUs / this._seekPanelWidth) * details.localPosition.dx) / 1000 / 1000;
                    var duration = Duration(seconds: seekPosition.floor());
                    var index = await this.manifest.getMediaSegmentIndexByDuration(duration);
                    this.pointer = index;
                    await cache();
                    this._videoPlayerController.seekTo(duration);
                  },
                  child: CustomPaint(
                    painter: BlockedSliderPainter(
                        value: position,
                        blocks: this._videoSegments,
                        rtl: false,
                        deactivatedColor: Colors.white.withOpacity(0.1),
                        activatedColor: Colors.white.withOpacity(0.5),
                        indicatorColor: Colors.white
                    ),
                    child: SizedBox(width: double.infinity, height: double.infinity,),
                  ),
                ),
              ),
              SizedBox(width: 5.0,),
              Text(this._getStringWithDuration(_videoPlayerController.value.position,), textAlign: TextAlign.center,),
              SizedBox(width: 5.0,),
            ],
          ),
          borderRadius: 8.0
      ),
    );
  }

  Widget _buildSegmentLoadingProgress() {
    return StreamBuilder<int>(
      stream: this._mediaSegmentLoadingProgress,
      builder: (context, snapshot) {
        if (snapshot.hasError) return Center(child: Text("An error occurred while loading media segments, here is the error message: ${snapshot.error}", textAlign: TextAlign.center,),);
        if (snapshot.hasData) return Stack(
          children: <Widget>[
            Center(child: _buildBlurFrame(child: SizedBox(width: 50.0, height: 50.0,)),),
            Center(child: CircularProgressIndicator(strokeWidth: 0.5,value: snapshot.data / 100, backgroundColor: Colors.white.withOpacity(0.04), valueColor: AlwaysStoppedAnimation<Color>(Colors.white.withOpacity(0.5)),),),
            Center(child: Center(child: Text(snapshot.data.toString()),),),
          ],
        );
        return Center(child: Text("Waiting..."),);
      },
    );
  }
}
class MyLinePainter extends CustomPainter {
  final double value;
  final Color activatedColor;
  final Color deactivatedColor;
  final Axis axis;

  MyLinePainter({this.axis, this.activatedColor, this.deactivatedColor, this.value}) : assert(value <= 1.0 && value >= 0.0);
  @override
  void paint(Canvas canvas, Size size) {
    var p1 = Offset(0.0, size.height / 2);
    var p2 = Offset(value * size.width, size.height / 2);
    var p3 = p2;
    var p4 = Offset(size.width, size.height / 2);
    if (axis == Axis.vertical) {
      p1 = Offset(size.width / 2, 0.0);
      p2 = Offset(size.width / 2, size.height * value);
      p3 = p2;
      p4 = Offset(size.width / 2, size.height);
    }
    canvas.drawLine(p1, p2, Paint()..color = activatedColor..strokeWidth = 1.0);
    canvas.drawLine(p3, p4, Paint()..color = deactivatedColor..strokeWidth = 1.0);
  }

  @override
  bool shouldRepaint(MyLinePainter oldDelegate) {
    var valid = true;
    valid &= this.value == oldDelegate.value;
    valid &= this.activatedColor == oldDelegate.activatedColor;
    valid &= this.deactivatedColor == oldDelegate.activatedColor;
    valid &= this.axis == oldDelegate.axis;
    return !valid;
  }
}

class _VideoRender extends StatefulWidget {
  final VideoPlayerController playerController;
  final void Function() refresh;

  const _VideoRender({Key key, this.playerController, this.refresh}) : super(key: key);
  @override
  State<StatefulWidget> createState() => new _VideoRenderState();

}

class _VideoRenderState extends State<_VideoRender> {
  int _second = 0;
  @override
  void initState() {
    this.widget.playerController.addListener(_handler);
    super.initState();
  }
  @override
  void dispose() {
    this.widget.playerController.removeListener(_handler);
    this.widget.playerController.dispose();
    super.dispose();
  }
  void _handler(){
    var current = this.widget.playerController.value.position.inSeconds;
    if (current != _second) {
      _second = current;
      this.widget.refresh();
    }
  }
  @override
  Widget build(BuildContext context) {
    return VideoPlayer(this.widget.playerController);
  }
}

class SliderBlock {
  final double length;
  final bool enabled;
  SliderBlock({this.enabled, this.length}) : assert (enabled != null && length != null);
}

//class _PlayerSeekbar extends StatefulWidget {
//  @override
//  State<StatefulWidget> createState() => new _PlayerSeekbarState();
//
//}
//
//class _PlayerSeekbarState extends State<_PlayerSeekbar> {
//  @override
//  Widget build(BuildContext context) {
//
//  }
//}

class BlockedSliderPainter extends CustomPainter {
  final List<SliderBlock> blocks;
  final bool rtl;
  final double value;
  final Color activatedColor;
  final Color deactivatedColor;
  final Color indicatorColor;

  BlockedSliderPainter({
    @required this.value,
    @required this.blocks,
    @required this.rtl,
    @required this.deactivatedColor,
    @required this.activatedColor,
    @required this.indicatorColor,
  }) : /*assert (value >= 0.0 && value <= 1.0), */assert(blocks.isNotEmpty/* && blocks.reduce((a, b) => SliderBlock(enabled: false, length: a.length + b.length)).length == 1.0*/);

  @override
  void paint(Canvas canvas, Size size) {
    var oldXPoint = 0.0;
    for (var block in this.blocks) {
      var p1 = Offset(oldXPoint, size.height / 2);
      var p2 = Offset(p1.dx + (block.length * size.width), size.height / 2);
      if (rtl) {
        p1 = Offset(size.width - p1.dx, size.height / 2);
        p2 = Offset(size.width - p2.dx, size.height / 2);
      }
      canvas.drawLine(p1, p2, Paint()..color = block.enabled ? activatedColor : deactivatedColor..strokeWidth = 1.0);
      oldXPoint += p2.dx - p1.dx;
    }
    var xPoint = this.value * size.width;
    if (rtl) xPoint = size.width - xPoint;
    canvas.drawCircle(Offset(xPoint, size.height / 2), 5, Paint()..strokeWidth = 0.0..color = this.indicatorColor);
//    canvas.drawArc(Rect.fromLTWH(xPoint, size.height / 2, 5.0, 5.0), 0.0, Math.pi * 2, true, Paint()..strokeWidth = 0.0..color = this.indicatorColor);
  }

  @override
  bool shouldRepaint(BlockedSliderPainter oldDelegate) {
    var valid = true;
    valid &= this.blocks.hashCode == oldDelegate.hashCode;
    valid &= this.value == oldDelegate.value;
    valid &= this.rtl == oldDelegate.rtl;
    valid &= this.activatedColor == oldDelegate.activatedColor;
    valid &= this.deactivatedColor == oldDelegate.deactivatedColor;
    valid &= this.indicatorColor== oldDelegate.indicatorColor;
    return !valid;
  }
}

class ListExtension {
  static T firstOrNull<T>(List<T> list) {
    if (list.isEmpty) return null; else return list.first;
  }
}


